Today’s schedule

Packages

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(terra)
## Warning: package 'terra' was built under R version 4.4.1
## terra 1.8.10
## 
## Attaching package: 'terra'
## 
## The following object is masked from 'package:tidyr':
## 
##     extract
library(sf)
## Warning: package 'sf' was built under R version 4.4.1
## Linking to GEOS 3.11.0, GDAL 3.5.3, PROJ 9.1.0; sf_use_s2() is TRUE
library(leaflet)
library(leaflet.extras)
## Warning: package 'leaflet.extras' was built under R version 4.4.1

Leaflet

Leaflet is….

…a Javascript library with an API we can access in R.

Wait, that’s a LOT of jargon. Can we add some clarity?

Features

  • Interactive panning/zooming
  • Compose maps using arbitrary combinations of:
    • Map tiles
    • Markers
    • Polygons
    • Lines
    • Popups
    • GeoJSON

Functionality

  • Create maps right from the R console or RStudio
  • Embed maps in knitr/R Markdown documents and Shiny apps
  • Easily render spatial objects from the sp or sf packages, or data frames with latitude/longitude columns
  • Use map bounds and mouse events to drive Shiny logic
  • Display maps in non spherical mercator projections
  • Augment map features using chosen plugins from leaflet plugins repository

Demonstration

A simple setup

m <- leaflet()

m

What do you get?

Let’s try again

m <- leaflet() %>%
  addTiles()

m

And now? What do you see?

Let’s build on that

m <- leaflet() %>%
  addTiles() %>%  # Add default OpenStreetMap map tiles
  addMarkers(lng = -81.350903, lat = 41.150377, popup="Our McGilvrey Hall GIS Lab")

m

What about now?

Let’s add some complexity

Create some data to plot. Let’s break this down a bit. What am I trying to do?

# First let's define a color palette we can sample from - this is only really necessary for the demo
mypal <- RColorBrewer::brewer.pal(12, "Set3")

# start with a data frame
df <- data.frame(
  lat = rnorm(100) * 2 + 41,
  lng = rnorm(100) * 2 - 81.5,
  size = runif(100, 5, 20),
  color = sample(mypal, 100, replace = T)
)

# then add the data frame to a leaflet map
m2 <- leaflet(df) %>% addTiles()

m2

What do you get? Why?

How can we interrogate the properties/attributes of an object?

The $ operator

m2$x
## $options
## $options$crs
## $crsClass
## [1] "L.CRS.EPSG3857"
## 
## $code
## NULL
## 
## $proj4def
## NULL
## 
## $projectedBounds
## NULL
## 
## $options
## named list()
## 
## attr(,"class")
## [1] "leaflet_crs"
## 
## 
## $calls
## $calls[[1]]
## $calls[[1]]$method
## [1] "addTiles"
## 
## $calls[[1]]$args
## $calls[[1]]$args[[1]]
## [1] "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
## 
## $calls[[1]]$args[[2]]
## NULL
## 
## $calls[[1]]$args[[3]]
## NULL
## 
## $calls[[1]]$args[[4]]
## $calls[[1]]$args[[4]]$minZoom
## [1] 0
## 
## $calls[[1]]$args[[4]]$maxZoom
## [1] 18
## 
## $calls[[1]]$args[[4]]$tileSize
## [1] 256
## 
## $calls[[1]]$args[[4]]$subdomains
## [1] "abc"
## 
## $calls[[1]]$args[[4]]$errorTileUrl
## [1] ""
## 
## $calls[[1]]$args[[4]]$tms
## [1] FALSE
## 
## $calls[[1]]$args[[4]]$noWrap
## [1] FALSE
## 
## $calls[[1]]$args[[4]]$zoomOffset
## [1] 0
## 
## $calls[[1]]$args[[4]]$zoomReverse
## [1] FALSE
## 
## $calls[[1]]$args[[4]]$opacity
## [1] 1
## 
## $calls[[1]]$args[[4]]$zIndex
## [1] 1
## 
## $calls[[1]]$args[[4]]$detectRetina
## [1] FALSE
## 
## $calls[[1]]$args[[4]]$attribution
## [1] "&copy; <a href=\"https://openstreetmap.org/copyright/\">OpenStreetMap</a>,  <a href=\"https://opendatacommons.org/licenses/odbl/\">ODbL</a>"
## 
## 
## 
## 
## 
## attr(,"leafletData")
##          lat       lng      size   color
## 1   40.74603 -83.45007 14.868927 #FFFFB3
## 2   40.20238 -83.95451 14.550825 #B3DE69
## 3   40.98750 -80.41588  6.697696 #80B1D3
## 4   39.10575 -79.84838 11.197219 #FB8072
## 5   44.50473 -79.46529 15.629754 #FFFFB3
## 6   38.86650 -82.54185 14.863234 #FDB462
## 7   44.36183 -80.83767  7.206468 #FFFFB3
## 8   42.03602 -81.11311 19.936633 #CCEBC5
## 9   41.39381 -79.46374 17.007305 #FDB462
## 10  42.48659 -82.08278  6.493354 #FB8072
## 11  40.80987 -83.19531  7.996221 #FFFFB3
## 12  39.86277 -77.69739 15.482108 #FB8072
## 13  44.65650 -84.85271  6.098189 #FFED6F
## 14  36.94411 -79.84225 13.999388 #FDB462
## 15  41.01350 -82.37728  6.456319 #FB8072
## 16  39.57375 -81.48592 10.648559 #8DD3C7
## 17  43.30297 -81.79619 11.616977 #FDB462
## 18  41.89682 -82.75958 17.169873 #BEBADA
## 19  41.28814 -83.07938 12.344856 #BC80BD
## 20  38.45777 -82.99757 17.311140 #CCEBC5
## 21  37.70709 -82.26975 17.806325 #FDB462
## 22  38.52106 -82.91888 10.976237 #FFED6F
## 23  40.06122 -79.84932 18.684877 #FFED6F
## 24  40.50728 -81.36894 16.058527 #CCEBC5
## 25  39.64461 -80.48374 11.757635 #D9D9D9
## 26  44.41391 -79.83604  6.791354 #FB8072
## 27  41.81229 -80.86726 12.304616 #80B1D3
## 28  42.48402 -79.62170 11.080573 #FFED6F
## 29  43.31210 -80.94017 18.126557 #FCCDE5
## 30  42.43452 -79.15316 13.598137 #FB8072
## 31  40.52948 -79.75909 17.280855 #FB8072
## 32  40.41377 -79.91344 10.574191 #B3DE69
## 33  40.25365 -81.38667 19.672723 #8DD3C7
## 34  43.97015 -80.59741 17.916000 #FB8072
## 35  41.59492 -82.07272 11.451747 #FCCDE5
## 36  40.40464 -79.13279 12.413794 #FFFFB3
## 37  37.99826 -79.51608  8.264504 #FFED6F
## 38  43.21010 -81.53139  5.097577 #80B1D3
## 39  43.50818 -79.66151  7.496173 #8DD3C7
## 40  42.40478 -77.73546 11.907850 #FFFFB3
## 41  39.36782 -77.01632 12.054958 #BEBADA
## 42  40.29865 -82.38083 14.969194 #BC80BD
## 43  41.44668 -85.48388 10.018350 #B3DE69
## 44  40.88648 -83.96119  9.522050 #FFFFB3
## 45  40.54576 -79.72585 11.245230 #FCCDE5
## 46  40.10957 -80.15598 16.897455 #B3DE69
## 47  40.55463 -82.66070  6.504108 #FDB462
## 48  38.38378 -81.80725  9.853867 #D9D9D9
## 49  42.03522 -80.76773  8.670518 #80B1D3
## 50  38.35694 -80.94591  9.284026 #FCCDE5
## 51  40.16155 -83.76884 15.224541 #FDB462
## 52  41.05248 -81.26068  8.927646 #80B1D3
## 53  41.69041 -79.44301  9.838585 #80B1D3
## 54  38.05285 -80.92638  6.394559 #BEBADA
## 55  39.63057 -86.17669 13.859316 #FDB462
## 56  39.75909 -81.29235 11.423585 #BEBADA
## 57  40.62700 -82.19091 15.393758 #FFED6F
## 58  39.57994 -83.38346 14.177956 #FFED6F
## 59  40.50729 -82.04547  7.566029 #FFFFB3
## 60  39.28100 -82.79194 16.650551 #FDB462
## 61  40.10139 -80.74254  9.426662 #80B1D3
## 62  42.09426 -82.32802 15.803093 #CCEBC5
## 63  45.09770 -77.17280 16.566673 #80B1D3
## 64  40.74408 -81.56063 12.441043 #CCEBC5
## 65  41.60859 -82.54916 17.646481 #BC80BD
## 66  41.78560 -79.56002 16.197787 #80B1D3
## 67  41.53633 -82.00549 17.957299 #8DD3C7
## 68  40.28095 -81.64869 14.459434 #FFED6F
## 69  44.54700 -81.09271 19.898664 #80B1D3
## 70  41.29158 -82.17957  5.698990 #FB8072
## 71  39.58424 -79.30207 17.209757 #D9D9D9
## 72  41.20083 -81.68176  5.039658 #BEBADA
## 73  40.98879 -82.89103 16.068044 #80B1D3
## 74  42.66432 -78.40134  5.706808 #FFED6F
## 75  40.96343 -81.55521 14.731461 #FB8072
## 76  39.30024 -82.58334 14.519423 #CCEBC5
## 77  38.83713 -80.61441 19.572098 #FFED6F
## 78  40.24927 -82.65505 13.563011 #D9D9D9
## 79  40.29989 -79.26816 18.643867 #FB8072
## 80  41.99950 -80.56377  9.462960 #8DD3C7
## 81  39.38699 -80.54877  5.027068 #FCCDE5
## 82  41.84487 -84.68819 19.769341 #BC80BD
## 83  37.38107 -81.29843 15.476084 #BEBADA
## 84  41.31889 -82.17798 19.927976 #FDB462
## 85  43.69980 -80.43696 10.123885 #FFED6F
## 86  39.61725 -80.69869  9.010128 #B3DE69
## 87  42.33963 -79.91979 19.147547 #FFED6F
## 88  39.78807 -82.03843  9.966668 #FDB462
## 89  42.50417 -81.00021  8.954779 #BEBADA
## 90  40.12446 -82.79217 14.121531 #8DD3C7
## 91  39.91140 -80.72159  9.905358 #CCEBC5
## 92  40.34544 -80.11924 15.923880 #BC80BD
## 93  41.38325 -82.82173  7.905780 #FDB462
## 94  41.51646 -78.67355 19.341440 #FB8072
## 95  43.03387 -82.51782  5.318999 #BC80BD
## 96  37.89447 -83.81203  6.779462 #BC80BD
## 97  41.46053 -78.43137 13.795615 #CCEBC5
## 98  37.46981 -82.65104  7.482956 #BEBADA
## 99  42.63798 -83.79103 14.194038 #FFFFB3
## 100 41.15436 -82.08332  8.673613 #FDB462

What do the data look like?

Let’s try again to visualize it. Again, break down the code first

# first one
m2 %>% addCircleMarkers(radius = ~size, color = ~color, fill = FALSE)
## Assuming "lng" and "lat" are longitude and latitude, respectively
# second one
m2 %>% addCircleMarkers(radius = runif(100, 4, 10), color = c('red'))
## Assuming "lng" and "lat" are longitude and latitude, respectively

What happened this time???

Let’s add some more tiles

# Let's check out some other tiles

m <- leaflet() %>% setView(lng = -81.34, lat = 41.145, zoom = 14)
m %>% addTiles()
# third party tiles using addProvider() function

m %>% addProviderTiles(providers$Stadia.StamenToner)
m %>% addProviderTiles(providers$CartoDB.Positron)
m %>% addProviderTiles(providers$CartoDB.DarkMatter)
m %>% addProviderTiles(providers$Esri.NatGeoWorldMap)

Give it a shot. What do you like?

Let’s use some of our data

parks <- sf::read_sf("../data/static_mapping/oh_parks.gpkg") %>%
  sf::st_transform(., "EPSG:4326") # transform to WGS84 to make Leaflet happy

# set up the map, zoom out a bit
mp <- leaflet(data = parks) %>% setView(lng = -81.34, lat = 41.145, zoom = 10)
mp %>% addTiles() %>% 
  addPolygons(popup = ~NAME, label = ~NAME)

What’s the difference between popup and label?

Lines

Who wants to help break down this code?

portage_streams <- sf::read_sf("../data/static_mapping/tl_2022_39133_linearwater/tl_2022_39133_linearwater.shp") %>%
  sf::st_transform(., "EPSG:4326") # transform to WGS84 to make Leaflet happy

leaflet(data = portage_streams) %>% 
  setView(lng = -81.34, lat = 41.145, zoom = 10) %>% 
  addTiles() %>%
  addPolylines(., color = "blue", 
               popup = ~paste0(FULLNAME, ": ", LINEARID))

What happened?

Multiple layers

(Note, there’s a difference here)

m.both <- leaflet() %>%
  setView(lng = -81.34, lat = 41.145, zoom = 10) %>% 
  addProviderTiles(providers$Esri.NatGeoWorldMap) %>%
  addPolygons(data = parks, popup = ~NAME, label = ~NAME, color = "green") %>%
  addPolylines(data = portage_streams, color = "blue", 
               popup = ~paste0(FULLNAME, ": ", LINEARID))

m.both

Let’s practice

Team activity

Tasks

GROUP 1

  • Make a choropleth map of the population density of Ohio Counties. There should be no more than 7 classes
  • When the user “mouses over” a polygon, they should highlight, and a pop-up with the county name and population density should appear
  • Place a legend in the bottom-right corner

Extra challenges:

  1. Add a minimap to the interface
  2. Add a “locate me” button to the interface

GROUP 2

  • Make a map of the monitoring stations and removed dams in the Chesapeake Bay Watershed
  • The monitoring stations should be symbolized as a filled, semi-transparent circle with the size scaled to the Drainage Area of the station
  • Dam layer should be shaded by the year it was removed
  • Add controls to turn individual layers on and off

Extra challenges:

  1. Use a custom icon for the dam layer
  2. Add a control to the interface to change the base map